#ifndef _tenacitas_distributed_test_0001_h_
#define _tenacitas_distributed_test_0001_h_

#include <sstream>
#include <string>
#include <iostream>
#include <memory>
#include <cstdint>

#include <boost/thread/mutex.hpp>

#include <tenacitas_distributed/server.h>
#include <tenacitas_async/event.h>
#include <tenacitas_log/logger.h>

namespace tenacitas {
    namespace distributed {
        namespace test_1 {

            /** The connection */
            class connection {

                friend std::ostream& operator << ( std::ostream& p_out,
                    const connection & p_conn ) {
                        p_out << p_conn.m_val;
                        return p_out;
                }

            public:
                typedef std::shared_ptr<connection> ptr_t;

                connection( int16_t p_val )
                    : m_val ( p_val ) {}

                connection( const connection & p_conn ) {
                    std::cout << "               COPY 1" << std::endl;
                    m_val = p_conn.m_val;
                }

                connection( const connection && p_conn ) {
                    std::cout << "               MOVE 1" << std::endl;
                    m_val = p_conn.m_val;
                }

                connection & operator = ( const connection & p_conn ) {
                    std::cout << "               COPY 2 " << std::endl;
                    m_val = p_conn.m_val;
                    return *this;
                }

                connection & operator = ( const connection && p_conn ) {
                    std::cout << "               MOVE 2 " << std::endl;
                    m_val = p_conn.m_val;
                    return *this;
                }


                int16_t val( ) {
                    return m_val;
                }

                std::ostream & operator << ( std::ostream & p_out ) {
                    p_out << m_val;
                    return p_out;
                }


            private:
                int16_t m_val;
            };


            /** The event associated to a connection */
            typedef async::event1<connection_event_id, connection> event_t;

            typedef event_t::ptr_t event_ptr_t;

            /** The one that receives connections */
            class receiver {

            public:
                typedef shared_ptr<receiver> ptr_t;

                receiver( )
                    : m_counter( 0 ) {}

                event_ptr_t operator( )( ) {

                    if ( m_counter < 20 ) {
                        return event_ptr_t( new event_t( connection( ++m_counter ) ) );
                    }
                    return event_ptr_t( );
                }

            private:
                uint16_t m_counter;
            };



            /** The handler class that will handle the event */
            template <typename t_logger>
            class handler {
            public:

                /** The type of pointer of the handler */
                typedef std::shared_ptr<handler> ptr_t;

                /** Type for the logger used  */
                typedef t_logger logger_t;                

                /** Contructor */
                handler( )
                    : m_mutex( ) {}

                /** The operator that handles the event */
                void operator( ) ( event_ptr_t p_event ) {

                    boost::lock_guard<boost::mutex> l_lock(m_mutex);

                    log_begin( logger_t );

                    // taking it easy...
                    boost::this_thread::sleep(boost::posix_time::seconds(2));


                    log_info( logger_t ) << "\t\tevent "
                        << event_t::id << ": "
                        << std::get<0>( *p_event ).val( );

                    log_end( logger_t );
                }

            private:
                /** A handler is to be called in a multi-threaded envinronment */
                boost::mutex m_mutex;
            };

            // The handler creator class
            template <typename t_logger>
            class handler_creator {
            public:

                /** Type for the logger used */
                typedef t_logger logger_t;

                /** type of the pointer */
                typedef std::shared_ptr<handler_creator> ptr_t;

                /**  Method that will create the first 2 handler_ptr_t objects */
                template<typename t_handlers>
                void first_creation( event_id &&, t_handlers & p_handlers ) {

                    typedef handler<logger_t> handler_t;
                    typedef typename handler_t::ptr_t handler_ptr_t;

                    for ( uint16_t l_counter = 0; l_counter < 2; ++l_counter ) {
                        p_handlers.add( handler_ptr_t( new handler_t ( ) ) );
                    }
                }

                // Method that will create additional 1 handler::ptr_t object
                template<typename t_handlers>
                bool additional_creation( event_id &&,
                    t_handlers & p_handlers ) {
                        // p_handlers.add( handler_ptr_t( new handler_t( ) ) );
                        // return true;
                        return false;
                }
            private:
                /** Single instance */
                static ptr_t m_instance;
            };




            /** Runs the test

            @tparam t_logger is left as a template, so that the same logger
            can be used by many tests
            */
            template <typename t_logger>
            void run( ) {

                /** Type for the logger */
                typedef t_logger logger_t;

                typedef handler_creator<logger_t> handler_creator_t;

                typedef typename handler_creator_t::ptr_t handler_creator_ptr_t;

                typedef receiver receiver_t;
                typedef receiver_t::ptr_t receiver_ptr_t;

                typedef handler<logger_t> handler_t;


                /** Type for the server */
                typedef server<
                    logger_t,
                    handler_t,
                    handler_creator_t,
                    receiver_t,
                    event_t> server_t;

                server_t l_server( receiver_ptr_t( new receiver_t( ) ),
                    handler_creator_ptr_t( new handler_creator_t( ) ) );

                // lets go!!
                log_begin( logger_t );

                l_server.start( );

                log_end( logger_t );

            }



        }
    }
}

#endif
